<?php

namespace App\Http\Requests\Cart;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use App\Models\Options\Option;
use App\Models\Products\Product;
use App\Models\Products\ProductOption;
use App\Models\Products\ProductOptionValue;
use App\Services\Locale\Language;
use App\Services\Utils\Memorable;
use Illuminate\Database\Eloquent\Collection;

trait ProductOptionRequest
{
    /**
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    public function checkProductOptions(): void
    {
        // must check required options first
        $request_options = $this->option ?: [];
        $request_option_ids = array_keys($request_options);
        $product_options = ProductOption::memoWhereGet(['product_id' => $this->product_id]);

        // 验证选项option_id是否是必须的
        $this->checkRequiredOptions($request_option_ids, $product_options);

        // if no options, excess / values check shall passed
        if (empty($request_options)) { return; }

        // 验证option_id 是否是此商品的
        $this->checkExcessOptions($request_option_ids, $product_options->modelKeys());

        // 验证option_value_id 是否是此商品的
        $this->checkProductOptionValues($request_options, $product_options);

        // 验证quantity 是否符合product_option_value表中的数量 (放到checkProductOptionValues（）中了)
//        $this->checkProductOptionValueQuantity($request_options, $product_options);

    }

    /**
     * check request option ids that not exist in product options
     *
     * @param array $request_option_ids
     * @param array $product_option_ids
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    private function checkExcessOptions(array $request_option_ids, array $product_option_ids): void
    {
        $excess_option_ids = array_diff($request_option_ids, $product_option_ids);
        if (count($excess_option_ids)) {
            $error_ids = implode(Language::localeComma(), $excess_option_ids);
            throw new ApiException(
                ApiErrCode::REQUEST_PARAMS_NOT_IN_RANGE,
                "option ids [$error_ids] not belongs to product: $this->product_id"
            );
        }
    }

    /**
     * check product options needed
     *
     * @param array $request_option_ids
     * @param ProductOption[]|Collection $product_options
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    private function checkRequiredOptions(array $request_option_ids, Collection $product_options): void
    {
        $required_options = $product_options->pluck('required', 'product_option_id')->filter()->all();
        $lack_option_ids = array_diff(array_keys($required_options), $request_option_ids);
        $lack_options = $product_options
            ->load('optionDescription')
            ->whereIn('product_option_id', $lack_option_ids)
            ->pluck('optionDescription.name')
            ->all();

        if (count($lack_options)) {
            $error_msg = implode(Language::localeComma(), $lack_options);
            throw new ApiException(
                ApiErrCode::CUSTOMIZE_ERROR,
                trans('checker.product_option_is_required', ['tip' => $error_msg])
            );
        }
    }

    /**
     * @param array $request_options
     * @param Collection|ProductOption[] $prod_options
     */
    private function checkProductOptionValues(array $request_options, Collection $prod_options)
    {
        $prod_options
            ->load(['option', 'productOptionValues'])
            ->each(function (ProductOption $prod_option) use ($request_options) {
                $product_option_id = $prod_option->product_option_id;
                if (! isset($request_options[$product_option_id])) {
                    return;
                }

                // check selection type
                if (in_array($prod_option->option->type, Option::SELECTION_TYPES)) {
                    $request_value_ids = (array) $request_options[$product_option_id];
                    $product_value_ids = $prod_option->productOptionValues->modelKeys();
                    $invalid_value_ids = array_diff($request_value_ids, $product_value_ids);
                    if (count($invalid_value_ids)) {
                        $this->validatorFailed("option[$product_option_id]", 'In', $product_value_ids);
                    }
                }

                // 验证商品选项的库存数量
                $this->checkProductOptionValueStock($request_options, $prod_option, $product_option_id);

                // check date format for date / time
//                if (in_array($prod_option->option->type, Option::DATE_TIME_TYPES)) {
//                    $value = $request_options[$product_option_id];
//
//                    $match_data_time = preg_match('//');
//                }

                // check file code for type file

            });

    }

    /**
     * @param $request_options
     * @param $product_option
     * @param $product_option_id
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    private function checkProductOptionValueStock($request_options, $product_option, $product_option_id)
    {
        $quantity = $this->quantity;
        $product_option_value_id = (array) $request_options[$product_option_id];

        $product_option_value_quantity = $product_option->productOptionValues->where('subtract', ProductOptionValue::PRODUCT_OPTION_VALUE_SUBTRACT)
            ->where('quantity', '<', $quantity)
            ->whereIn('product_option_value_id', $product_option_value_id)
            ->first();

        if ($product_option_value_quantity) {
            throw new ApiException(
                ApiErrCode::CUSTOMIZE_ERROR,
                trans('checker.product_option_value_quantity')
            );
        }
    }

    /**
     * @param array $request_options
     * @param Collection $prod_options
     */
    private function checkProductOptionValueQuantity(array $request_options, Collection $prod_options)
    {
        $prod_options
            ->load(['option', 'productOptionValues'])
            ->each(function (ProductOption $prod_option) use ($request_options) {
                $product_option_id = $prod_option->product_option_id;
                if (! isset($request_options[$product_option_id])) {
                    return;
                }

                $quantity = $this->quantity;
                $request_value_ids = (array) $request_options[$product_option_id];

                $product_option_value_quantity = $prod_option->productOptionValues->where('subtract', ProductOptionValue::PRODUCT_OPTION_VALUE_SUBTRACT)
                    ->where('quantity', '<', $quantity)
                    ->whereIn('product_option_value_id', $request_value_ids)
                    ->first();

                if ($product_option_value_quantity) {
                    throw new ApiException(
                        ApiErrCode::CUSTOMIZE_ERROR,
                        trans('checker.product_option_value_quantity')
                    );
                }

            });
    }

    /**
     * @throws \Exception
     */
    public function checkProductExist()
    {
        $this->getProduct();
    }

    /**
     * @return Product
     *
     * @throws \Exception
     */
    private function getProduct()
    {
        $find = Product::memoFind($this->product_id);
        if (! $find) {
            $this->validatorFailed('product', 'Exists');
        }

        return $find;
    }

    /**
     * @throws \Exception
     */
    public function checkProductQuantity()
    {
        $product = $this->getProduct();

        // 大于等于最小起订数量
        $minimum_quantity = $product->minimum ?? Product::PRODUCT_MINIMUM_QUANTITY;
        if ($this->quantity < $minimum_quantity) {
            $this->validatorFailed('quantity', "Min", [$minimum_quantity]);
        }

        // 小于等于商品总库存数量
        $total_quantity = $product->quantity ?? Product::PRODUCT_QUANTITY_DEFAULT;
        if ($total_quantity < $this->quantity) {
            $this->validatorFailed('quantity', "Max", [$total_quantity]);
        }
    }
}